/*
 * Hash.cpp
 *
 *  Created on: 11/10/2011
 *      Author: gisedaye
 */

#include "Hash.h"

Hash::Hash(string hashFilename) {
	this->bucketsFile=hashFilename;
	this->freeBucketsFile=hashFilename+"_free";
	if(!FileLoader::fileExists(this->bucketsFile)){
		FileLoader::createFile(this->bucketsFile);
		FileLoader::createFile(this->freeBucketsFile);
	}
	this->hashTable= new HashTable(hashFilename+"_table");
}

void Hash::insertValue(string id,string value){
	Record * record= new Record(id, value);
	Bucket * bucket=this->searchRecord(record);
	bool overflow=false;
	bucket->insertRecord(record,&overflow);

	if(overflow)
		this->overflow(bucket,record);
	bucket->writeToFile(this->bucketsFile);
	if(this->hashTable->getSize()==0)
		this->hashTable->insertValue(0,0);

}

void Hash::modifyValue(string id,string value){
	Record * record= new Record(id, value);
	Bucket * bucket=this->searchRecord(record);
	bool overflow=false;
	bucket->modifyRecord(record,&overflow);

	if(overflow)
		this->overflow(bucket,record);

	bucket->writeToFile(this->bucketsFile);

}

void Hash::deleteValue(string id){
	Record * record= new Record(id, "");
	Bucket * bucket=this->searchRecord(record);
	bool underflow=false;
	bucket->deleteRecord(record,&underflow);

	if(underflow)
		this->underflow(bucket,record);
	bucket->writeToFile(this->bucketsFile);

}

Bucket * Hash::searchRecord(Record * record){
	Bucket *bucket;
	if(this->hashTable->getSize()==0)
		bucket= new Bucket(0);
	else{
		string bucketNumber=this->hashTable->searchRecord(record);
		cout<<"BUCKETNUMBER"<<bucketNumber<<endl;
		string bucketString=FileLoader::getBucketFromFile(this->bucketsFile,Utils::stringToInt(bucketNumber));
		bucket= new Bucket(-1);
		bucket->loadStringToClass(bucketString);
	}
	return bucket;
}

void Hash::underflow(Bucket * bucket,Record * record){
	int start=record->getDispersionNumber(this->hashTable->getSize());
	int jump=bucket->getDispersionNumber()/2;
	int backPosition=start-jump;
		if (backPosition<0){
			backPosition+=this->hashTable->getSize();
		}
		int forwardPosition = start+jump;
		if (forwardPosition>this->hashTable->getSize()){
			forwardPosition-=this->hashTable->getSize();
		}

		if (this->hashTable->getSize()!=1){
			if (this->hashTable->getValue(backPosition) == this->hashTable->getValue(forwardPosition)){
				int newValue = this->hashTable->getValue(backPosition);
				this->hashTable->modifyBucketNumberJumps(start,jump,newValue);
				if (this->hashTable->isMirrored()){
					this->hashTable->truncate();
				}
				this->freeBuckets.push_back(newValue);
				FileWritable::writeToFile(this->freeBucketsFile,Utils::intToString(newValue));

			}
		}
}

void Hash::overflow(Bucket * bucket,Record * record){
	int start=record->getDispersionNumber(this->hashTable->getSize());
	if(bucket->getDispersionNumber()<=this->hashTable->getSize()){
		this->hashTable->duplicateSize();
		this->hashTable->modifyBucketNumber(start,this->hashTable->getMaxBucketNumber()+1);
		this->hashTable->setMaxBucketNumber(this->hashTable->getMaxBucketNumber()+1);
		Bucket * newBucket=new Bucket(this->hashTable->getMaxBucketNumber());
		this->redisperseBuckets(bucket, newBucket);
		bucket->writeToFile(this->bucketsFile);
		newBucket->writeToFile(this->bucketsFile);
		delete newBucket;
	}
	else{
		//agrego bloque y redisperso
		this->hashTable->modifyBucketNumber(bucket->getDispersionNumber(),this->hashTable->getMaxBucketNumber()+1);
		this->hashTable->setMaxBucketNumber(this->hashTable->getMaxBucketNumber()+1);
		Bucket * newBucket=new Bucket(this->hashTable->getMaxBucketNumber());
		this->redisperseBuckets(bucket, newBucket);
		bucket->writeToFile(this->bucketsFile);
		newBucket->writeToFile(this->bucketsFile);
		//modifico referencias
		this->hashTable->modifyBucketNumberJumps(start,newBucket->getDispersionNumber()/2,this->hashTable->getMaxBucketNumber());
		delete newBucket;
	}

}

void Hash::redisperseBuckets(Bucket * bucket, Bucket * newBucket){
	int dispersionNumber;
	unsigned int size=bucket->getRecords().size();
	vector<int>positionErase;
	bool overflow=false;;
	unsigned int i;
	for(i=0;i<size;i++){
		dispersionNumber=bucket->getRecords()[i]->getDispersionNumber(this->hashTable->getSize());
		if(dispersionNumber==newBucket->getDispersionNumber()){
			newBucket->insertRecord(bucket->getRecords()[i],&overflow);
			positionErase.push_back(i);
		}
	}
	bool underflow=false;
	for(i=0;i<positionErase.size();i++){
		bucket->deleteRecord(bucket->getRecords()[positionErase[i]],&underflow);
	}
}

Hash::~Hash() {
	// TODO Auto-generated destructor stub
}
